package sg.edu.nus.iss.umpire.service.workflow;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import sg.edu.nus.iss.umpire.dao.jpa.UmpireDAOImpl;
import sg.edu.nus.iss.umpire.domain.Competition;
import sg.edu.nus.iss.umpire.domain.CompetitionDay;
import sg.edu.nus.iss.umpire.domain.Session;
import sg.edu.nus.iss.umpire.domain.Umpire;
import sg.edu.nus.iss.umpire.exception.ServiceException;
import sg.edu.nus.iss.umpire.service.workflow.dto.SessionDTO;
import sg.edu.nus.iss.umpire.support.AppConstants;
import sg.edu.nus.iss.umpire.support.HttpMethod;
import sg.edu.nus.iss.umpire.support.Service;
import sg.edu.nus.iss.umpire.support.ServiceConfig;
import sg.edu.nus.iss.umpire.support.ServiceContext;
import sg.edu.nus.iss.umpire.support.StringUtil;

import com.google.appengine.api.backends.BackendServiceFactory;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.taskqueue.Queue;
import com.google.appengine.api.taskqueue.QueueFactory;
import com.google.appengine.api.taskqueue.TaskOptions;
import com.google.appengine.api.taskqueue.TaskOptions.Method;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

@ServiceConfig(path = "workflow/service", method = HttpMethod.POST)
public class WorkflowPostServiceController implements Service {
	@Override
	public String serve(ServiceContext ctx) throws Exception {

		String action = ctx.getRequest().getParameter("action");

		if (StringUtil.isEmpty(action)) {
			return null;
		} else if ("sendInvitation".equals(action)) {
			return sendInvitation(ctx);
		} else if ("umpireReply".equals(action)) {
			return umpireReply(ctx);
		} else if ("updateSession".equals(action)) {
			return updateSession(ctx);
		} else if ("sendConfirmationEmail".equals(action)){
			return sendConfirmationEmail(ctx);
		}

		throw new ServiceException("Invalid parameter");
	}
	
	private String sendConfirmationEmail(ServiceContext ctx) {
		Long competitionId = Long.valueOf(ctx.getRequest().getParameter("id"));
	  	Queue queue = QueueFactory.getQueue("SendConfirmEmailQueue");
	  	TaskOptions taskOptions = TaskOptions.Builder.withUrl("/umpire/sendConfirmEmail")
	  							  .param("competitionId", String.valueOf(competitionId))
	  	                          .header("Host", BackendServiceFactory.getBackendService().getBackendAddress("send-confirm-email-backend"))
	  	                          .method(Method.POST);
	  	queue.add(taskOptions);
		
		return null;
	}

	private String sendInvitation(ServiceContext ctx) {
		// parse paremeters
		Long competitionKey = Long.valueOf(ctx.getRequest().getParameter("id"));
		String slots = ctx.getRequest().getParameter("timeslots");
		Competition competition = ctx.getEntityManager().find(Competition.class, competitionKey);
		
		List<SessionDTO> sessions = SessionDTO.fromJSON(slots);
		
		// create session for competitionday
		List<CompetitionDay> competitionDays = competition.getCompetitionDays();
		for (String date : getDates(sessions)) {
			CompetitionDay day = findDay(competitionDays, date);
			day.getSessions().clear();
			
			List<SessionDTO> selectedSlots = filterSession(sessions, day.getDate());
			for (SessionDTO s : selectedSlots) {
				Session session = new Session();
				session.setSlotName(s.getSlotName());
				session.setCompetitionDayKey(day.getCompetitionDayKey());
				day.getSessions().add(session);
			}
			
		}
		
		
		// Send email to all the umpires
		sendInvitationEmail(ctx);
		
		return "redirect:/s/competitions";
	}

	private void sendInvitationEmail(ServiceContext ctx) {
		Long competitionKey = Long.valueOf(ctx.getRequest().getParameter("id"));
		String instruction = ctx.getRequest().getParameter("instruction");

		
	    Queue queue = QueueFactory.getQueue("InvitationQueue");
	    TaskOptions taskOptions = TaskOptions.Builder.withUrl("/umpire/sendinvitations")
	        .param("instruction", instruction)
	        .param("competitionKey", String.valueOf(competitionKey))
	        .header("Host", BackendServiceFactory.getBackendService().getBackendAddress("send-confirm-email-backend"))
	        .method(Method.POST);
	    queue.add(taskOptions);
	}
	
	private String umpireReply(ServiceContext ctx) {
		
		Umpire user = ctx.getUserSession();
		String remarks = ctx.getRequest().getParameter("remarks");
		Long competitionId = Long.valueOf(ctx.getRequest().getParameter("id"));
		Competition competition = ctx.getEntityManager().find(Competition.class, competitionId);
		
		
		List<Response> reply = Response.fromJSON(ctx.getRequest().getParameter("availslot")); 
		for (CompetitionDay day : competition.getCompetitionDays()) {
			for (Session session : day.getSessions()) {
				if(positiveResponseForSlot(reply, day.getDate(), session.getSlotName())
						&& !session.getAvailUmpire().contains(user.getKey())){
					session.getAvailUmpire().add(user.getKey());
				}
			}
		}
		
		return "redirect:/s/competitions";
	}
	
	private String updateSession(ServiceContext ctx) {
			String keyStr = ctx.getRequest().getParameter("sessionKey");
			Key key = KeyFactory.stringToKey(keyStr);
			Session session = ctx.getEntityManager().find(Session.class, key);
			session.setAvailUmpire(parseIds(ctx.getRequest().getParameter("avail")));
			session.setConfirmedUmpire(parseIds(ctx.getRequest().getParameter("confirmed")));
			return null;
		}
	
	
	private List<Long> parseIds(String ids){
		if (StringUtil.isEmpty(ids)){
			return new ArrayList<Long>();
		}
		
		List<Long> idList = new ArrayList<Long>();
		for (String id : ids.split(",")) {
			idList.add(Long.valueOf(id));
		}
		return idList;
	}

	private boolean positiveResponseForSlot(List<Response> reply, String date, String slotName) {
		List<String> slotNames = Arrays.asList(new String[]{"Morning", "Afternoon", "Evening"});
		int index = slotNames.indexOf(slotName);
		if (index < 0)
			return false;
		
		for (Response response : reply) {
			if (response.getDate().equals(date)){
				return response.getSlots()[index];
			}
		}
		return false;
	}

	private Set<String> getDates(List<SessionDTO> sessions){
		Set<String> set = new HashSet<String>();
		for (SessionDTO session : sessions) {
			set.add(session.getDate());
		}
		
		return set;
	}
	
	private List<SessionDTO> filterSession(List<SessionDTO> list, String date){
		List<SessionDTO> result = new ArrayList<SessionDTO>();
		for (SessionDTO dto : list) {
			if(dto.getDate().equals(date)){
				result.add(dto);
			}
		}
		
		return result;
	}
	
	private CompetitionDay findDay(List<CompetitionDay> list, String date){
		for (CompetitionDay dto : list) {
			if(dto.getDate().equals(date)){
				return dto;
			}
		}
		
		return null;
	}
	
	private String format(Date date){
		SimpleDateFormat df = new SimpleDateFormat(AppConstants.DEFAULT_DATE_FORMAT);
		return df.format(date);
	}
	
	private static class Response{
		private String date;
		private Boolean[] slots;
		public String getDate() {
			return date;
		}
		public void setDate(String date) {
			this.date = date;
		}
		public Boolean[] getSlots() {
			return slots;
		}
		public void setSlots(Boolean[] slots) {
			this.slots = slots;
		}
		
		public static List<Response> fromJSON(String json){
			Gson gson = new Gson();
	    	return gson.fromJson(json, new TypeToken<List<Response>>(){}.getType());
		}
	}
}
